package mips.instructions;

import mips.Main;

/**
 * TODO fix execute(...) and emit(...)
 * 
 * <code>JALR</code> instruction<br/>
 * Jump And Link Register<br/>
 * @author jnmartin84@gmail.com
 */
public class JALR extends Instruction {

	private static final JALR INSTANCE = new JALR();
	private static final String INSTRUCTION_NAME = "JALR";

	private JALR(){}

	public static final JALR getInstance() {
		return INSTANCE;
	}

	/**
	 * <b>Format:</b><br/>
	 * JALR rs<br/>
	 * JALR rd, rs<br/><br/>
	 * <b>Description:</b><br/>
	 * The program unconditionally jumps to the address contained in general<br/>
	 * register rs, with a delay of one instruction. The address of the instruction<br/>
	 * after the delay slot is placed in general register rd. The default value of rd,<br/>
	 * if omitted in the assembly language instruction, is 31.<br/><br/>
	 * Register specifiers rs and rd may not be equal, because such an instruction<br/>
	 * does not have the same effect when re-executed. However, an attempt to<br/>
	 * execute this instruction is not trapped, and the result of executing such an<br/>
	 * instruction is undefined.<br/><br/>
	 * Since instructions must be word-aligned, a <b>Jump and Link Register</b><br/>
	 * instruction must specify a target register (rs) whose two low-order bits are<br/>
	 * zero. If these low-order bits are not zero, an address exception will occur<br/>
	 * when the jump target instruction is subsequently fetched.<br/><br/>
	 * <b>Operation:</b><br/>
	 * T: temp &larr; GPR [rs]<br/>
	 * GPR[rd] &larr; PC + 8<br/>
	 * T+1: PC &larr; temp
	 */
	@Override
	public final void execute(final int instruction) throws mips.exceptions.AddressErrorException {

		final int rs = (instruction >> 21) & 0x0000001F;
		final int rd = (instruction >> 11) & 0x0000001F;

		mips.R4300i.GPR[rd] = mips.R4300i.PC + 8;

		if((mips.R4300i.GPR[rs] & 0x00000003) == 0) {

			mips.R4300i.PC = mips.R4300i.nPC;
			// double-check this works right!
			mips.R4300i.nPC = (int)(mips.R4300i.GPR[rs] & 0x00000000FFFFFFFFL);

			if(Main.tracing) {
				mips.R4300i.targets.put(mips.R4300i.nPC,null);
			}
		}
		// this isn't implemented right, this shouldn't get thrown until the instruction fetch
		// this means we need to add a flag for the interpreting loop :o
		else {
			throw new mips.exceptions.AddressErrorException(mips.R4300i.PC,instruction);
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final String emit(final int instruction) {
		
		final int rs = (instruction >> 21) & 0x0000001F;
		final int rd = (instruction >> 11) & 0x0000001F;
		
		return	"		mips.CPU.GPR["+rd+"] = mips.CPU.PC + 8;\n" + 
				"		\n" + 
				"		if((mips.CPU.GPR["+rs+"] & 0x00000003) == 0) {\n" + 
				"			\n" + 
				"			mips.CPU.PC = mips.CPU.nPC;\n" + 
				"			// double-check this works right!\n" +
				"			mips.CPU.nPC = (int)(mips.CPU.GPR["+rs+"] & 0x00000000FFFFFFFFL);\n" +  
				"		}\n" + 
				"		else {\n" + 
				"			throw new mips.exceptions.AddressErrorException(mips.CPU.PC,"+String.format("0x%08X",instruction)+");\n" + 
				"		}\n";
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final String getName(final int instruction) {
		return getName();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final String getName() {
		return INSTRUCTION_NAME;
	}
}